package handler

import (
	"net/http"

	"database/sql"

	"gitee.com/hp-huiw/family-expense-api/internal/dto"
	"gitee.com/hp-huiw/family-expense-api/internal/service"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/gin-gonic/gin"
)

type UserHandler interface {
	QueryDetails(ctx *gin.Context)
	QueryUsersNotInAccountGroup(ctx *gin.Context)
	QueryAccountGroupUsers(ctx *gin.Context)
	UpdatePassword(ctx *gin.Context)
	UpdateCurrentUserPassword(ctx *gin.Context)
	UpdateCurrentUser(ctx *gin.Context)
	UpdateRoles(ctx *gin.Context)
}

// Returns the instance of private member, userHandler, which implements UserHandler interface.
func NewUserHandler(db *sql.DB) UserHandler {
	return &userHandler{
		userService: service.NewUserService(db),
	}
}

// Matches /query-details of http get action.
func (h *userHandler) QueryDetails(ctx *gin.Context) {
	c := ctx.Request.Context()
	id := ctx.Query("id")
	accountGroupID := GetClaims(ctx).AccountGroupID
	result, err := h.userService.QueryDetails(c, accountGroupID, id)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /query-users-not-in-account-group of http get action.
func (h *userHandler) QueryUsersNotInAccountGroup(ctx *gin.Context) {
	keyword := ctx.Query("keyword")
	pageSize, err := util.StringToInt64(ctx.Query("pageSize"))

	if err != nil {
		ctx.Error(err)
		return
	}

	pageIndex, err := util.StringToInt64(ctx.Query("pageIndex"))

	if err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	result, err := h.userService.QueryUsersNotInAccountGroup(c, accountGroupID, keyword, pageSize, pageIndex)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /query-account-group-users of http get action.
func (h *userHandler) QueryAccountGroupUsers(ctx *gin.Context) {
	pageSize, err := util.StringToInt64(ctx.Query("pageSize"))

	if err != nil {
		ctx.Error(err)
		return
	}

	pageIndex, err := util.StringToInt64(ctx.Query("pageIndex"))

	if err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	result, err := h.userService.QueryAccountGroupUsers(c, accountGroupID, pageSize, pageIndex)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /update-password of http post action.
func (h *userHandler) UpdatePassword(ctx *gin.Context) {
	var postedData struct {
		Username string `json:"username" binding:"required"`
		Password string `json:"password" binding:"required"`
	}

	if err := ctx.ShouldBindJSON(&postedData); err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	err := h.userService.UpdatePassword(c, accountGroupID, postedData.Username, postedData.Password)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendStatusResponse(ctx, http.StatusNoContent)
}

// Matches /update-current-user-password of http post action.
func (h *userHandler) UpdateCurrentUserPassword(ctx *gin.Context) {
	var postedData struct {
		CurrentPassword string `json:"currentPassword" binding:"required"`
		NewPassword     string `json:"newPassword" binding:"required"`
	}

	if err := ctx.ShouldBindJSON(&postedData); err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	claims := GetClaims(ctx)

	// Checks if the current password is correct. Otherwise the password will not be updated.
	isPasswordMatched, err := h.userService.ValidatePassword(c, claims.Username, postedData.CurrentPassword)

	if err != nil {
		ctx.Error(err)
		return
	}

	// If the current password matches then update to the new password.
	if isPasswordMatched {
		err := h.userService.UpdatePassword(c, claims.AccountGroupID, claims.Username, postedData.NewPassword)

		if err != nil {
			ctx.Error(err)
			return
		}
	}

	SendDataResponse(ctx, isPasswordMatched)
}

// Matches /update-current-user of http post action.
func (h *userHandler) UpdateCurrentUser(ctx *gin.Context) {
	var postedData struct {
		FullName    string  `json:"fullName" binding:"required"`
		Description *string `json:"description"`
	}

	if err := ctx.ShouldBindJSON(&postedData); err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	claims := GetClaims(ctx)
	err := h.userService.UpdateCurrentUser(c, claims.Username, postedData.FullName, postedData.Description)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendStatusResponse(ctx, http.StatusNoContent)
}

// Matches /update-roles of http post action.
func (h *userHandler) UpdateRoles(ctx *gin.Context) {
	var postedData dto.UserRolesRequest

	if err := ctx.ShouldBindJSON(&postedData); err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	claims := GetClaims(ctx)
	err := h.userService.UpdateRoles(c, claims.AccountGroupID, postedData)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendStatusResponse(ctx, http.StatusNoContent)
}

// Container of UserHandler functions.
// This can make NO conflict with other same name functions which attached to other handlers.
type userHandler struct {
	userService service.UserService
}
